package com.naiterui.ehp.bs.weixin.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.doctor.RecomPatientBO;
import com.naiterui.ehp.bp.bo.patient.PatientBO;
import com.naiterui.ehp.bp.support.security.SettingConfig;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.lang.StringUtil;
import com.naiterui.ehp.bs.weixin.config.SubscribeConfig;
import com.naiterui.ehp.bs.weixin.config.WxMaConfiguration;
import com.naiterui.ehp.bs.weixin.constant.WeixinConstant;
import com.naiterui.ehp.bs.weixin.constant.WeixinMaConstant;
import com.naiterui.ehp.bs.weixin.entity.mysql.WeixinInfo;
import com.naiterui.ehp.bs.weixin.exception.WeixinCode;
import com.naiterui.ehp.bs.weixin.exception.WeixinException;
import com.naiterui.ehp.bs.weixin.feign.PatientFeignClient;
import com.naiterui.ehp.bs.weixin.properties.WeixinProperties;
import com.naiterui.ehp.bs.weixin.repository.mysql.WeixinInfoRepository;
import com.naiterui.ehp.bs.weixin.security.api.JwtApi;
import com.naiterui.ehp.bs.weixin.security.bean.JwtInfo;
import com.naiterui.ehp.bs.weixin.service.UserService;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeEvent;
import com.naiterui.ehp.bs.weixin.subscribe.SubscribeProperties;
import com.naiterui.ehp.bs.weixin.vo.LoginUserVO;
import com.naiterui.ehp.bs.weixin.vo.LoginVO;
import com.naiterui.ehp.bs.weixin.vo.MiniappPhoneParamVO;
import com.naiterui.ehp.bs.weixin.vo.MiniappPhoneVO;
import com.naiterui.ehp.bs.weixin.vo.RegisterParamVO;
import com.naiterui.ehp.bs.weixin.vo.SubscribeTemplateListVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private final JwtApi jwtApi;
    private final WeixinProperties weixinproperties;
    private final WeixinInfoRepository weixinInfoRepository;

    private final PatientFeignClient patientFeignClient;
    private final SubscribeConfig subscribeConfig;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginVO miniappLogin(String code, String scene) throws WeixinException, WxErrorException {

        String appid = weixinproperties.getAppId();
        final WxMaService wxService = WxMaConfiguration.getMaService(appid);
        WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(code);
        log.info("小程序登录，获取到session信息为 sessionKey {}, openId {}, unionId {}", session.getSessionKey(), session.getOpenid(), session.getUnionid());
        String miniAppOpenId = session.getOpenid();
        String unionId = session.getUnionid();
        if (StringUtils.isEmpty(miniAppOpenId) || StringUtils.isEmpty(unionId)) {
            log.error("微信授权信息为空");
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }
        RedisUtil.hashOps().hset(WeixinConstant.USER_SESSIONKEY_CACHE, session.getOpenid(), session.getSessionKey());
        log.info("miniappLogin miniapp session info ; miniAppOpenId={},unionId={}", miniAppOpenId, unionId);
        // 根据openId查询
        PatientBO patient = patientFeignClient.getPatientInfo(null, miniAppOpenId, null);
        WeixinInfo weixinInfo = weixinInfoRepository.findByMiniOpenId(miniAppOpenId);

        // 如果微信信息不为空，代表是第二次进行登录
        if (weixinInfo != null) {
            log.info("小程序用户信息 {}", JsonMapper.toJson(weixinInfo));
            // 绑定miniOpenId
            patientFeignClient
                .bindMiniOpenId(unionId, miniAppOpenId, weixinInfo.getName(), weixinInfo.getAvatar(), weixinInfo.getGender(), weixinInfo.getPhone());
        } else {
            LoginUserVO userinfo = LoginUserVO.builder().openId(miniAppOpenId).build();
            log.info("没有获取到小程序信息，返回1提示前端进行注册用户，返回信息为 unionId {}, userInfo {}", unionId, JsonMapper.toJson(userinfo));
            return new LoginVO(LoginVO.LOGIN_UNREGISTER, unionId, userinfo);
        }

        //已授权用户可以正常登录,生成登录token
        JwtInfo jwtInfo = this.jwtApi.generateJwt(patient.getPatientId());
        if (jwtInfo == null) {
            log.error("miniappLogin jwt token generate failed ; user={}", patient);
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }


        // 记录用户登录态
//        RedisUtil.valueOps().set(CommonConstant.PT_APP_LOGIN_IN + patient.getPatientId(), "1");
//
//        // 已存在用户，无 miniAppOpenId或、openId
//        if (StringUtils.isNotBlank(unionId)) {
//            weixinInfo.setUnionId(unionId);
//            patient.setUnionId(unionId);
//        }
//        weixinInfo.setLastLogin(new Date());
//        weixinInfoRepository.update(weixinInfo);

        return LoginVO.builder().loginStatus(LoginVO.LOGIN_SUCCESS).userInfo(LoginUserVO
            .builder().openId(miniAppOpenId).userId(patient.getPatientId()).tokenKey(SettingConfig.TOKEN_HEADER)
            .token(jwtInfo.getToken()).avatar(weixinInfo.getAvatar()).name(weixinInfo.getName()).miniOpenId(weixinInfo.getMiniOpenId()).build())
            .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginUserVO miniappRegister(RegisterParamVO registerParamVO) throws WeixinException {

        String appid = weixinproperties.getAppId();
        final WxMaService wxService = WxMaConfiguration.getMaService(appid);

        // 用户信息校验
        String sessionKey = RedisUtil.hashOps().hget(WeixinConstant.USER_SESSIONKEY_CACHE, registerParamVO.getOpenId());
        if (!wxService.getUserService().checkUserInfo(sessionKey, registerParamVO.getRawData(), registerParamVO.getSignature())) {
            log.error("miniappRegister weicaht rawDate check failed ; sessionKey {},appid={},registerParamVO={}", sessionKey, appid, registerParamVO);
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }

        // 解密用户信息
        WxMaUserInfo userInfo = wxService.getUserService().getUserInfo(sessionKey, registerParamVO.getEncryptedData(), registerParamVO.getIv());
        log.info("获取到用户信息为 {}", JsonMapper.toJson(userInfo));
        if (userInfo == null) {
            log.error("miniappRegister wechat userInfo decode failed ; appid={},registerParamVO={}", appid, registerParamVO);
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }

        WeixinInfo weixinInfo = null;
        if (StrUtil.isNotEmpty(registerParamVO.getUnionId())) {
            weixinInfo = weixinInfoRepository.findByUnionId(registerParamVO.getUnionId());
            log.info("unionId:{}" + registerParamVO.getUnionId());
        }
        if (weixinInfo == null) {
            weixinInfo = WeixinInfo.builder().age(0).createdAt(new Date()).build();
        }

        if (StringUtils.isBlank(weixinInfo.getName())) {
            weixinInfo.setName(userInfo.getNickName());
        }
        weixinInfo.setAvatar(userInfo.getAvatarUrl());
        // 处理微信性别信息与代码解析不一致问题
        Integer gender = RecomPatientBO.GenderEnum.GENDER_UNKNOWN.getId();
        if (null != userInfo.getGender()) {
            RecomPatientBO.GenderEnum genderEnum = RecomPatientBO.GenderEnum.getByWeixinId(Integer.parseInt(userInfo.getGender()));
            if (null != genderEnum) {
                gender = genderEnum.getId();
            }
        }
        weixinInfo.setGender(gender);
        weixinInfo.setMiniOpenId(registerParamVO.getOpenId());
        weixinInfo.setUnionId(registerParamVO.getUnionId());
        weixinInfo.setPhone(registerParamVO.getPhone());
        weixinInfo.setAccountStatus(WeixinConstant.UserStatus.NORMAL);
        //weixinInfo.setLastLogin(new Date());
        weixinInfo.setChangedAt(new Date());

        weixinInfo = weixinInfoRepository.saveOrUpdate(weixinInfo);

        // 绑定miniOpenId
        Boolean isBind = patientFeignClient
            .bindMiniOpenId(registerParamVO.getUnionId(), registerParamVO.getOpenId(), weixinInfo.getName(), weixinInfo.getAvatar(),
                weixinInfo.getGender(), weixinInfo.getPhone());
        if (!isBind) {
            log.error("miniappRegister bind miniOpenId error; userInfo:{}", userInfo);
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }

        // 根据unionId查询
        PatientBO patient = patientFeignClient.getPatientInfo(registerParamVO.getUnionId(), null, null);
        if (patient == null) {
            log.error("miniappRegister wechat patient not exist; userInfo:{}", userInfo);
            return null;
        }

        // 完善就诊人姓名信息
        patientFeignClient.supplementMyselfInfo(patient.getPatientId(), weixinInfo.getName());

        JwtInfo jwtInfo = this.jwtApi.generateJwt(patient.getPatientId());

        return LoginUserVO.builder().openId(registerParamVO.getOpenId()).tokenKey(SettingConfig.TOKEN_HEADER)
            .userId(patient.getPatientId()).token(jwtInfo.getToken()).avatar(weixinInfo.getAvatar()).name(weixinInfo.getName())
            .miniOpenId(weixinInfo.getMiniOpenId()).build();
    }

    /**
     * 服务号取关
     *
     * @param openId
     * @param wxMpService
     */
    @Override
    public void mpUnsubscribe(String openId, WxMpService wxMpService) {
    }

    /**
     * 服务号关注、扫码
     *
     * @param openId
     * @param eventKey
     * @param wxMpService
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public WeixinInfo mpRegister(String openId, String eventKey, WxMpService wxMpService) throws WxErrorException {

        WeixinInfo weixinInfo = weixinInfoRepository.findByMpOpenId(openId);
        if (weixinInfo == null) {
            WxMpUser wxMpUser = wxMpService.getUserService().userInfo(openId);
            weixinInfo = weixinInfoRepository.findByUnionId(wxMpUser.getUnionId());
            if (weixinInfo == null) {
                weixinInfo = WeixinInfo.builder()
                    .age(0)
                    .mpOpenId(openId)
                    .unionId(wxMpUser.getUnionId())
                    .avatar(wxMpUser.getHeadImgUrl())
                    .createdAt(new Date())
                    .changedAt(new Date())
                    .gender(wxMpUser.getSex())
                    .name(wxMpUser.getNickname())
                    .accountStatus(WeixinConstant.UserStatus.INIT)
                    .build();
            }
            weixinInfo.setMpOpenId(openId);
            weixinInfoRepository.saveOrUpdate(weixinInfo);
        }

        return weixinInfo;
    }

    @Override
    public boolean existPhone(Long userId) {
        WeixinInfo weixinInfo = weixinInfoRepository.get(userId);
        return weixinInfo != null && StringUtil.isMobileNum(weixinInfo.getPhone());
    }

    @Override
    public MiniappPhoneVO miniappPhone(MiniappPhoneParamVO miniappPhoneParamVO) throws WeixinException {
        String appid = weixinproperties.getAppId();
        final WxMaService wxService = WxMaConfiguration.getMaService(appid);

        // 用户信息校验
        String sessionKey = RedisUtil.hashOps().hget(WeixinConstant.USER_SESSIONKEY_CACHE, miniappPhoneParamVO.getOpenId());
        if (StringUtils.isBlank(sessionKey)) {
            log.error("miniappPhone  sessionKey failed ; appid={},miniappPhoneParamVO={}", appid, miniappPhoneParamVO);
            throw new WeixinException(WeixinCode.WECHAT_LOGIN_FAIL);
        }
        WxMaPhoneNumberInfo numberInfo = wxService.getUserService()
            .getPhoneNoInfo(sessionKey, miniappPhoneParamVO.getEncryptedData(), miniappPhoneParamVO.getIv());
        return MiniappPhoneVO.builder()
            .countryCode(numberInfo.getCountryCode())
            .phoneNumber(numberInfo.getPhoneNumber())
            .purePhoneNumber(numberInfo.getPurePhoneNumber())
            .build();
    }

    @Override
    public void sendCode(Long userId, String phone) throws WeixinException {
        if (!StringUtil.isMobileNum(phone)) {
            log.error("sendCode fail ; illegal : userId={},phone={}", userId, phone);
            throw new WeixinException(WeixinCode.SMS_PHONE_CHECK_FAIL);
        }
        String redisKey = getCodeCacheKey(userId, phone);
        String code = RedisUtil.valueOps().getString(redisKey);
        if (code != null) {
            log.error("sendCode alread send ; userId={},phone={},code={}", userId, phone, code);
            throw new WeixinException(WeixinCode.SMS_CODE_ALREAD_SEND);
        }
        code = RandomUtils.nextLong(1000, 9999) + "";
        String[] data = new String[]{code, "10"};
        // TODO: 2021/2/5 发短信api
        /*SMSUtil.send(WeixinConstant.CHINA_CODE.concat(phone), SmsTemplateIdConstant.VERIFY_CODE, data);*/
        RedisUtil.valueOps().setEx(redisKey, code, WeixinConstant.CODE_CATCHE_EXPIRATION);
    }

    @Async
    @Override
    public void handleUserSubscribe(String miniOpenId, Object list) {
        Long patientId = patientFeignClient.getPatientIdByMiniOpenId(miniOpenId);
        if (null == patientId) {
            log.debug("该小程序openid未绑定患者, miniOpenId: {}", miniOpenId);
        }
        // 处理用户授权小程序订阅消息的处理
        List<SubscribeProperties> template = subscribeConfig.getTemplate();
        String templateId = template.stream().filter(s -> s.getLabel().equals(SubscribeTemplateListVO.LABEL_INQUIRY_REMINDER))
            .map(SubscribeProperties::getTemplateId).collect(Collectors.joining());
        if (list instanceof JSONArray) {
            List<SubscribeEvent> subscribeEvents = JSONUtil.toList((JSONArray) list, SubscribeEvent.class);
            subscribeEvents.forEach(s -> {
                if (templateId.equals(s.getTemplateId())) {
                    RedisUtil.hashOps().hset(WeixinMaConstant.WECHAR_MA_SUBSCRIBE,
                        SubscribeTemplateListVO.LABEL_INQUIRY_REMINDER + "-" + patientId, s.getSubscribeStatus());
                }
            });
            return;
        }
        SubscribeEvent subscribeEvent = JSONUtil.toBean((JSONObject) list, SubscribeEvent.class);
        if (templateId.equals(subscribeEvent.getTemplateId())) {
            RedisUtil.hashOps().hset(WeixinMaConstant.WECHAR_MA_SUBSCRIBE, SubscribeTemplateListVO.LABEL_INQUIRY_REMINDER + "-" + patientId,
                subscribeEvent.getSubscribeStatus());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void varifyCode(Long userId, String phone, String code) throws WeixinException {
        String redisKey = getCodeCacheKey(userId, phone);
        String codeCache = RedisUtil.valueOps().getString(redisKey);
        if (!StringUtils.equals(code, codeCache)) {
            log.error("varifyCode fail ; code not match : userId={},phone={},code={},codeCache={}", userId, phone, code, codeCache);
            throw new WeixinException(WeixinCode.SMS_CODE_NOT_MATCH);
        }
        RedisUtil.keyOps().delete(redisKey);

        WeixinInfo weixinInfo = weixinInfoRepository.get(userId);
        if (weixinInfo == null) {
            log.error("varifyCode fail ; user not exist : userId={},phone={},code={},codeCache={}", userId, phone, code, codeCache);
            throw new WeixinException(WeixinCode.USER_NOT_EXIST);
        }
        weixinInfo.setPhone(phone);
        weixinInfo.setChangedAt(new Date());
        weixinInfoRepository.update(weixinInfo);
    }

    private String getCodeCacheKey(Long userId, String phone) {
        return WeixinConstant.CODE_CATCHE_PROFIX + userId + "_" + phone;
    }

}
